/*
 * Reflector JFrame
 * Display an image with a set number of reflections
 * Luke Mitchell 09-04-12 <lm0466@my.bristol.ac.uk>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 /*
  * Some notes on the nomenclature:
  *
  * An 'image' is a BufferedImage, specifically that displayed by
  * the ImagePanel
  *
  * A 'picture' is the stored BufferedImage, local to this class
  * that is displayed, in part or full,
  * rotated or reflected.
  *
  */
 
import javax.swing.*;
import javax.imageio.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import java.io.File;
import java.lang.Double;
import java.lang.Boolean;
 
 class JReflectorFrame extends JFrame implements ActionListener
 {
	/* Private variables */
	
	private BufferedImage picture;
    private ImagePanel frame;
	
	private Rectangle picture_area_visible;
	private int segments;
	
	private JButton segs_1, segs_2, segs_4, segs_8;
 
	/* Private methods */
	
	// Deal with an error
    private void error (String msg)
    {
        System.err.println ("Error: " + msg);
    }
	
	// 'creates' an image with one segment
    // in reality it just returns
    private BufferedImage reflect_1s (BufferedImage img)
    {
        return img;
    }

    // creates an image with two segments
    // the line of symmetry is vertical
    // TODO - take into account centre point
    private BufferedImage reflect_2s (BufferedImage img, Point centre)
    {
        BufferedImage assembled, half;
        int w = (img.getWidth () / 2);
        int[] row = new int[w];
        
        assembled = new BufferedImage (img.getWidth (), img.getHeight (), BufferedImage.TYPE_INT_ARGB);
        half = new BufferedImage (w, img.getHeight (), BufferedImage.TYPE_INT_ARGB);
                
        // copy left half as-is
        // this is copied to the final image and a buffer
        for (int y = 0; y < img.getHeight (); y++)
        {
            row = img.getRGB (0, y, w, 1, row, 0, 1);
            half.setRGB (0, y, w, 1, row, 0, 1);
            assembled.setRGB (0, y, w, 1, row, 0, 1);
        }

        // flip and repeat for RHS
        half = BufferedImageUtils.reflect_vertical (half);

        for (int y = 0; y < half.getHeight (); y++)
        {
            row = half.getRGB (0, y, w, 1, row, 0, 1);
            assembled.setRGB (w, y, w, 1, row, 0, 1);
        }

        // return
        return assembled;
    }

    // creates an image with n segments
    // n can be 1, 2, 4 or 8
    private BufferedImage reflect_ns (BufferedImage img, int n, Point centre)
    {
        BufferedImage assembled = new BufferedImage (img.getWidth (), img.getHeight (), BufferedImage.TYPE_INT_ARGB);
        BufferedImage tl = null, tr, bl, br;

        int maxy = (int) (centre.getY ());
        int maxx = (int) (centre.getX ());

        switch (n)
        {
            case 1:
                return reflect_1s (img);

            case 2:
                return reflect_2s (img, centre);

            case 4:
                int[] row = new int[maxx];

                tl = new BufferedImage (maxx, maxy, BufferedImage.TYPE_INT_ARGB);

                // create TL segment
                for (int y = 0; y < maxy; y++)
                {
                    row = img.getRGB (0, y, maxx, 1, row, 0, 1);
                    tl.setRGB (0, y, maxx, 1, row, 0, 1);
                }

                break;

            case 8:
                Point origin = new Point (0, 0);
                tl = new BufferedImage (maxx, maxy, BufferedImage.TYPE_INT_ARGB);

                Triangle t1 = new Triangle (origin, new Point (maxx, 0), centre);
                Point[] p1 = t1.contained();

                for (int k = 0; k < p1.length; k++)
                    tl.setRGB (Common.dtoi (p1[k].getX ()), Common.dtoi (p1[k].getY ()),
                        img.getRGB (Common.dtoi(p1[k].getX ()), Common.dtoi (p1[k].getY ())));

                Triangle t2 = new Triangle (origin, new Point (0, maxy), centre);
                Point[] p2 = t2.contained();
             
                // swap x and y for second segment
                for (int k = 0; k < p2.length; k++)
                    tl.setRGB (Common.dtoi (p2[k].getY ()), Common.dtoi (p2[k].getX ()),
                        img.getRGB (Common.dtoi(p2[k].getX ()), Common.dtoi (p2[k].getY ())));

                break;
        }

        // the TR segment is reflected vertically
        tr = BufferedImageUtils.reflect_vertical (tl);

        // the BL segment is reflected horizontally
        bl = BufferedImageUtils.reflect_horizontal (tl);

        // the BR segment is reflected in both directions
        br = BufferedImageUtils.reflect_both (tl);

        // compose the final image
        // N.B. maxx and maxy are also the segment dimensions
        // used here somewhat confusingly
        for (int y = 0; y < img.getHeight (); y++)
        {
            int[] row = new int[maxx];

            for (int x = 0; x < img.getWidth (); x += maxx)
            {
                // TL
                if (y < maxy && x < maxx)
                {
                    row = tl.getRGB (0, y, maxx, 1, row, 0, 1);
                    assembled.setRGB (0, y, maxx, 1, row, 0, 1);
                    continue;
                }

                // TR
                if (y < maxy && x >= maxx)
                {
                    row = tr.getRGB (0, y, maxx, 1, row, 0, 1);
                    assembled.setRGB (maxx, y, maxx, 1, row, 0, 1);
                    continue;
                }

                // BL
                if (y >= maxy && x < maxx)
                {
                    if ((y - maxy) < maxy)
                    {
                        row = bl.getRGB (0, (y - maxy), maxx, 1, row, 0, 1);
                        assembled.setRGB (0, y, maxx, 1, row, 0, 1);
                    }
                    
                    continue;
                }

                // BR
                if (y >= maxy && x >= maxx)
                {
                    if ((y - maxy) < maxy)
                    {
                        row = br.getRGB (0, (y - maxy), maxx, 1, row, 0, 1);
                        assembled.setRGB (maxx, y, (maxx - 1), 1, row, 0, 1);
                    }
                    continue;
                }
            }
        }

        return assembled;
    }

    // sets the number of segments in the kaleidoscope
    private Boolean set_segments (int n)
    {
        // TODO - check valid n
        segments = n;

        if (frame != null)
        {
            set_image_as_picture ();
        }

        return true;
    }
	
	// sets the image to display as the visible region of the picture
	private void set_image_as_picture ()
	{
		set_image_as_picture (false);
	}
	
	private void set_image_as_picture (Boolean resize)
	{
		BufferedImage visible_picture;

		if (resize)
		{
			set_segments (1);
			visible_picture = picture;
		}
		else
		{
			visible_picture = picture.getSubimage (	(int) picture_area_visible.getX (),
													(int) picture_area_visible.getY (),
													(int) picture_area_visible.getWidth (),
													(int) picture_area_visible.getHeight ());
		}
		
		set_image (visible_picture, resize);
	}
	
    // sets the image to display
    private void set_image (BufferedImage img)
    {
		set_image (img, false);
    }
	
    private void set_image (BufferedImage img, Boolean resize)
    {
		int initialWidth = 0, initialHeight = 0;
		int width, height;
		
		if (resize)
		{	
			initialWidth = frame.getWidth ();
			initialHeight = frame.getHeight ();
		}
			
        BufferedImage kscoped = reflect_ns (img, segments, BufferedImageUtils.image_centre (img));
        
        if (frame != null)
        {
            frame.set_image (kscoped);
        }

		if (resize)
		{
			// new size constraints
			width =  this.getWidth () + (frame.getWidth () - initialWidth);
			height =  this.getHeight () + (frame.getHeight () - initialHeight);
			
			// set picture constraints
			picture_area_visible.setLocation (0, 0);
			picture_area_visible.setSize (img.getWidth (), img.getHeight ());
			
			// resize to fit
			set_window_size (width, height);
		}
    }
	
	// Determines whether an image has been set
    private Boolean image_set ()
    {
        if (picture == null)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
	
	// sets the window size
	private void set_window_size (int w, int h)
	{
		Dimension d = new Dimension (w, h);
	
		// restrict resize to image dimensions
		setMaximumSize (d);
		setPreferredSize (d);
		
		// set the size
		setSize (w, h);
	}
 
	// Create the menu
    private JMenuBar menu ()
    {
        JMenuBar menu = new JMenuBar ();

        // File menu - for loading/saving images
        JMenu file = new JMenu ("File");
        file.setMnemonic (KeyEvent.VK_F);

        JMenuItem file_load = new JMenuItem ("Load image", KeyEvent.VK_L);
        file_load.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_L, ActionEvent.CTRL_MASK));
        file_load.setActionCommand ("LOAD");
        file_load.addActionListener (this);
        file.add (file_load);
        
        JMenuItem file_save = new JMenuItem ("Save image", KeyEvent.VK_S);
        file_save.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_S, ActionEvent.CTRL_MASK));
        file_save.setActionCommand ("SAVE");
        file_save.addActionListener (this);
        file.add (file_save);

        JMenuItem file_exit = new JMenuItem ("Exit", KeyEvent.VK_X);
        file_exit.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_X, ActionEvent.CTRL_MASK));
        file_exit.setActionCommand ("EXIT");
        file_exit.addActionListener (this);
        file.add (file_exit);

        menu.add (file);

        // Image menu - for applying transforms
        JMenu image = new JMenu ("Image");
        image.setMnemonic (KeyEvent.VK_I);

        JMenuItem image_cw = new JMenuItem ("Rotate CW", KeyEvent.VK_R);
        image_cw.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_R, ActionEvent.CTRL_MASK));
        image_cw.setActionCommand ("CW");
        image_cw.addActionListener (this);
        image.add (image_cw);
        
        JMenuItem image_ccw = new JMenuItem ("Rotate CCW", KeyEvent.VK_E);
        image_ccw.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_E, ActionEvent.CTRL_MASK));
        image_ccw.setActionCommand ("CCW");
        image_ccw.addActionListener (this);
        image.add (image_ccw);

        JMenuItem image_reflect_h = new JMenuItem ("Reflect horizontally", KeyEvent.VK_D);
        image_reflect_h.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_D, ActionEvent.CTRL_MASK));
        image_reflect_h.setActionCommand ("REFLECT_H");
        image_reflect_h.addActionListener (this);
        image.add (image_reflect_h);

        JMenuItem image_reflect_v = new JMenuItem ("Reflect vertically", KeyEvent.VK_F);
        image_reflect_v.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_F, ActionEvent.CTRL_MASK));
        image_reflect_v.setActionCommand ("REFLECT_V");
        image_reflect_v.addActionListener (this);
        image.add (image_reflect_v);
        
        JMenuItem image_centre = new JMenuItem ("Centre image", KeyEvent.VK_C);
        image_centre.setAccelerator(KeyStroke.getKeyStroke
            (KeyEvent.VK_C, ActionEvent.CTRL_MASK));
        image_centre.setActionCommand ("CENTRE");
        image_centre.addActionListener (this);
		image_centre.setEnabled (false);
        image.add (image_centre);

        menu.add (image);

        return menu;
    }

	// Create the toolbar
    private JToolBar toolbar ()
    {
        JToolBar toolbar = new JToolBar("Reflection segments");
        
        segs_1 = new JButton ();
        segs_1.setToolTipText ("Display the original image");
        segs_1.setText ("1");
        segs_1.setActionCommand ("BTN_SEGS_1");
        segs_1.addActionListener (this);
        toolbar.add (segs_1);
        
        segs_2 = new JButton ();
        segs_2.setToolTipText ("Display the image with 2 segments");
        segs_2.setText ("2");
        segs_2.setActionCommand ("BTN_SEGS_2");
        segs_2.addActionListener (this);
        toolbar.add (segs_2);

        segs_4 = new JButton ();
        segs_4.setToolTipText ("Display the image with 4 segments");
        segs_4.setText ("4");
        segs_4.setActionCommand ("BTN_SEGS_4");
        segs_4.addActionListener (this);
        toolbar.add (segs_4);

        segs_8 = new JButton ();
        segs_8.setToolTipText ("Display the image with 8 segments. This can only be performed on square images.");
        segs_8.setText ("8");
        segs_8.setActionCommand ("BTN_SEGS_8");
        segs_8.addActionListener (this);
		segs_8.setEnabled (false);
        toolbar.add (segs_8);

        toolbar.setFloatable (false);

        return toolbar;
    }

	/* Public methods */
	
	// Override the paint method to prevent the
	// frame being resized beyond its maximum size.
	@Override
    public void paint(Graphics g)
	{
		Dimension d = getSize();
        Dimension m = getMaximumSize();
		
        boolean resize = d.width > m.width || d.height > m.height;
		
        d.width = Math.min(m.width, d.width);
        d.height = Math.min(m.height, d.height);
		
        if (resize)
		{
			Point p = getLocation();
            setVisible(false);
            setSize(d);
            setLocation(p);
            setVisible(true);
        }
		else
		{
			picture_area_visible.setSize (frame.getWidth (), frame.getHeight ());
			
			// is the image square?
			if (picture_area_visible.getWidth () == picture_area_visible.getHeight ())
			{
				segs_8.setEnabled (true);
			}
			else
			{
				set_segments (1);
				segs_8.setEnabled (false);
			}
			
			set_image_as_picture ();
		}
		
        super.paint(g);
    }
	
	/* ActionListener methods */

    public void actionPerformed(ActionEvent e)
    {
        String cmd = e.getActionCommand ();
       
        if (cmd == "LOAD" || cmd == "SAVE")
        {
                JFileChooser chooser = new JFileChooser();
                SimpleFileFilter filter = new SimpleFileFilter();

            if (cmd == "LOAD") {
				filter.addExtension("jpg");
                filter.addExtension("gif");
                filter.addExtension("png");
                filter.addExtension("bmp");
                filter.setDescription("Image files");
                chooser.setFileFilter(filter);
			
                int returnVal = chooser.showOpenDialog(null);
                
                if (returnVal == JFileChooser.APPROVE_OPTION)
                {
                    File f = chooser.getSelectedFile();
					picture = BufferedImageUtils.load_image (f);
                    set_image_as_picture (true);
                }
            } else if (cmd == "SAVE") {
                filter.addExtension("png");
                filter.setDescription("PNG (*.png)");
                chooser.setFileFilter(filter);
			
                int returnVal = chooser.showSaveDialog(null);
                
                if (returnVal == JFileChooser.APPROVE_OPTION)
                {
                    File f = chooser.getSelectedFile();
					Image img = frame.get_image ();
                    ImageUtils.save_image (img, f);
                }
            }
        } else if (cmd == "EXIT") {
            System.exit (0);
        } else if (cmd == "CW") {
			picture = BufferedImageUtils.rotate_90_clockwise (picture);
            set_image_as_picture ();
        } else if (cmd == "CCW") {
			picture = BufferedImageUtils.rotate_90_anticlockwise (picture);
            set_image_as_picture ();
        } else if (cmd == "REFLECT_H") {
            picture = BufferedImageUtils.reflect_horizontal (picture);
            set_image_as_picture ();
        } else if (cmd == "REFLECT_V") {
			picture = BufferedImageUtils.reflect_vertical (picture);
            set_image_as_picture ();
        } else if (cmd == "CENTRE") {
            // TODO
            set_image_as_picture ();
        } else if (cmd == "BTN_SEGS_1") {
            set_segments (1);
        } else if (cmd == "BTN_SEGS_2") {
            set_segments (2);
        } else if (cmd == "BTN_SEGS_4") {
            set_segments (4);
        } else if (cmd == "BTN_SEGS_8") {
			set_segments (8);
        }
    }
	
	/* Constructor */
	
	// Construct the UI and initialise components
	public JReflectorFrame ()
	{
		// instantiate some object
		picture_area_visible = new Rectangle ();
		segments = 1;
	
		// setup the frame
		setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
        setTitle ("Reflector");
        setLayout (new BorderLayout ());
		setLocationByPlatform (true);
        setResizable (true);
		
		// add a menu
        setJMenuBar (menu ());

        // and a toolbar
        add (toolbar(), BorderLayout.PAGE_START);

        // add the image panel
		picture = BufferedImageUtils.load_image ("img/logo.png");
        frame = new ImagePanel (picture);
        add (frame);

        if (!image_set())
        {
            error ("Failed to load image!");
        }

		// pack the ui
        pack ();
        validate ();
		
		// load the splash image
		// this sets the default size
		picture_area_visible.setSize (frame.getWidth (), frame.getHeight ());
		set_image_as_picture (true);
		
		// et voila!
		setVisible (true);
	}
 }